package com.hucsmn.chatroom;

import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;

/**
 * 聊天室客户端。
 */
public class ChatClient {
    /**
     * 默认连接地址 127.0.0.1。
     */
    public static final InetAddress ADDRESS = Inet4Address.getLoopbackAddress();

    /**
     * 默认连接端口 8888。
     */
    public static final int PORT = 8888;

    /**
     * 套接字超时时间。
     */
    private static final int TIMEOUT = 5000;

    // 连接地址。
    private final InetAddress address;
    private final int port;
    private String nick;

    /**
     * 创建聊天室客户端，连接默认地址和端口号。
     */
    ChatClient() {
        this(ADDRESS, PORT);
    }

    /**
     * 创建聊天室客户端，连接默认地址和指定端口号。
     *
     * @param port 连接端口号。
     */
    ChatClient(int port) {
        this(ADDRESS, port);
    }

    /**
     * 创建聊天室客户端，连接指定地址和端口号。
     *
     * @param address 连接地址。
     * @param port 连接端口号。
     */
    ChatClient(InetAddress address, int port) {
        if (address == null) {
            throw new NullPointerException();
        }
        if (port <= 0) {
            throw new IllegalArgumentException();
        }
        this.address = address;
        this.port = port;
    }

    /**
     * 聊天室消息频道。
     */
    public static class MessageChannel implements Closeable, AutoCloseable {
        // 包装的套接字
        private final Socket socket;
        private final BufferedReader in;
        private final Writer out;

        private MessageChannel(Socket socket) throws IOException {
            if (socket == null) {
                throw new NullPointerException();
            }
            socket.setSoTimeout(0);
            this.socket = socket;
            this.in = new BufferedReader(
                    new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8));
            this.out = new OutputStreamWriter(socket.getOutputStream(), StandardCharsets.UTF_8);
        }

        /**
         * 发送消息。
         *
         * @param message 消息文字。
         * @throws IOException 网络异常。
         */
        public void say(String message) throws IOException {
            message = message.replace('\n', ' ');
            out.write(message + "\n");
            out.flush();
        }

        /**
         * 接收下一条消息。
         *
         * @return 消息文字。
         * @throws IOException 网络异常。
         */
        public String listen() throws IOException {
            return in.readLine();
        }

        /**
         * 关闭聊天室。
         *
         * @throws IOException 网络异常。
         */
        @Override
        public void close() throws IOException {
            socket.close();
        }
    }

    /**
     * 请求进行聊天。
     *
     * @return 聊天连接套接字。
     * @throws IOException 网络异常。
     */
    public MessageChannel chat() throws IOException {
        // 请求连接。
        Socket sock = new Socket();
        sock.connect(new InetSocketAddress(address, port), TIMEOUT);
        sock.setSoTimeout(TIMEOUT);

        // 写入聊天请求的头信息。
        ChatHeaders.writeHeader(sock.getOutputStream(), "CHAT", nick);

        return new MessageChannel(sock);
    }

    /**
     * 请求发送文件。
     *
     * @param file 文件位置。
     * @throws IOException 文件读写或网络异常。
     */
    public void send(File file) throws IOException {
        if (file == null) {
            throw new NullPointerException();
        }

        // 打开文件和套接字。
        try (InputStream in = new FileInputStream(file);
             Socket sock = new Socket()) {
            sock.connect(new InetSocketAddress(address, port), TIMEOUT);

            // 写入发送请求的头信息。
            sock.setSoTimeout(TIMEOUT);
            OutputStream out = sock.getOutputStream();
            ChatHeaders.writeHeader(out, "SEND", file.getName());

            // 开始数据流复制。
            sock.setSoTimeout(0);
            StreamCopy.copy(in, out);
            sock.shutdownOutput();

            // 获取传输结果。
            sock.setSoTimeout(TIMEOUT);
            String[] header = ChatHeaders.readHeader(sock.getInputStream());
            if (header.length == 0) {
                throw new MalformedChatHeaderException("miss send status");
            }
            switch (header[0].toUpperCase()) {
                case "SUCCESS":
                    break;
                case "FAIL":
                    String info = ChatHeaders.headerArgumentsToString(header);
                    throw new IOException("chat server failed to receive '" + file + "': " + info);
                default:
                    throw new MalformedChatHeaderException("bad status type");
            }
        }
    }

    /**
     * 请求接收文件。
     *
     * @param name 远程待接收文件名。
     * @param file 本地保存文件。
     * @throws IOException 文件读写或网络异常，
     */
    public void receive(String name, File file) throws IOException {
        if (name == null || file == null) {
            throw new NullPointerException();
        }
        if (name.length() == 0) {
            throw new IllegalArgumentException();
        }

        // 打开文件和套接字。
        try (OutputStream out = new FileOutputStream(file); //TODO: 使用临时文件接收数据
             Socket sock = new Socket()) {
            sock.connect(new InetSocketAddress(address, port), TIMEOUT);

            // 写入接收请求的头信息。
            sock.setSoTimeout(TIMEOUT);
            ChatHeaders.writeHeader(sock.getOutputStream(), "RECV", name);

            // 获取文件状态头信息。
            InputStream in = sock.getInputStream();
            String[] header = ChatHeaders.readHeader(in);
            if (header.length == 0) {
                throw new MalformedChatHeaderException("miss receive status");
            }
            switch (header[0].toUpperCase()) {
                case "FOUND":
                    break;
                case "NOTFOUND":
                    String info = ChatHeaders.headerArgumentsToString(header);
                    throw new FileNotFoundException("chat server failed to find '" + name + "': " + info);
                default:
                    throw new MalformedChatHeaderException("bad status type");
            }

            // 开始数据流复制。
            sock.setSoTimeout(0);
            StreamCopy.copy(in, out);
        }
    }

    /**
     * 获取昵称。
     *
     * @return 昵称。
     */
    public String getNick() {
        return nick;
    }

    /**
     * 设置昵称。
     *
     * @param nick 昵称。
     */
    public void setNick(String nick) {
        if (nick.length() == 0 || nick.length() > 32) {
            throw new IllegalArgumentException();
        }
        this.nick = nick;
    }

    /**
     * 获取连接的聊天服务器地址。
     *
     * @return 聊天服务器地址。
     */
    public InetAddress getAddress() {
        return address;
    }

    /**
     * 获取连接的聊天服务器端口号。
     *
     * @return 聊天服务器端口号。
     */
    public int getPort() {
        return port;
    }
}
